﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace DeepHash.CommandParser
{
    public class Parser
    {
        public class CommandParseResult
        {
            private Command _command = null;
            public Command Command
            {
                get { return _command; }
            }

            private bool _errorOccurred = false;
            public bool ErrorOccurred
            {
                get { return _errorOccurred; }
            }

            private string _errorMessage = string.Empty;
            public string ErrorMessage
            {
                get { return _errorMessage; }
            }

            internal CommandParseResult(Command cmd, bool isError, string error)
            {
                _command = cmd;
                _errorOccurred = isError;
                _errorMessage = error;
            }
        }

        public Parser()
        {
        }

        private string _parameterSpecifier = "/";
        public string ParameterSpecifier
        {
            get { return _parameterSpecifier; }
            set { _parameterSpecifier = value; }
        }

        private string _parameterValueStarter = " ";
        public string parameterValueStarter
        {
            get { return _parameterValueStarter; }
            set { _parameterValueStarter = value; }
        }

        private Dictionary<string, CommandDefinition> _commands = new Dictionary<string, CommandDefinition>();
        public Dictionary<string, CommandDefinition> Commands
        {
            get { return _commands; }
        }

        private CommandOption _commandKeyword = null;
        public CommandOption CommandKeyword
        {
            get { return _commandKeyword; }
            set { _commandKeyword = value; }
        }

        private Dictionary<string, CommandOption> _options = new Dictionary<string, CommandOption>();
        public Dictionary<string, CommandOption> Options
        {
            get { return _options; }
        }

        private string GetParameterValue(string[] paramTokens)
        {
            StringBuilder sb = new StringBuilder();
            if (paramTokens.Length > 1)
            {
                for (int i = 1; i < paramTokens.Length; i++)
                {
                    sb.Append(paramTokens[i]);
                    if (i < paramTokens.Length - 1)
                    {
                        sb.Append(this.parameterValueStarter);
                    }
                }
            }

            return sb.ToString();
        }

        public CommandParseResult ParseCommand(string[] argv)
        {
            string commandLine = string.Join(" ", argv);
            string[] parms = commandLine.Split (this.ParameterSpecifier.ToCharArray());
            Dictionary<string, string> commandParts = new Dictionary<string, string>();

            StringBuilder sbError = new StringBuilder();

            foreach (string token in parms)
            {
                if (!string.IsNullOrEmpty(token))
                {
                    string[] tokenParts = token.Split(this.parameterValueStarter.ToCharArray());
                    if (!string.IsNullOrEmpty(tokenParts[0]))
                    {
                        commandParts[tokenParts[0].Trim()] = GetParameterValue(tokenParts).Trim();
                    }
                }
            }

            Command cmd = null;
            // Since we have parsed all command parts, we can now go and build the command
            if (commandParts.ContainsKey(this.CommandKeyword.Name))
            {
                // Well we have found a command. We need to verify and validate if all items in
                // the command were specified as needed
                string validationErrors = ValidateCommandOptions(commandParts, commandParts[this.CommandKeyword.Name]);
                sbError.Append(validationErrors);
                if (string.IsNullOrEmpty(validationErrors))
                {
                    cmd = GetCommand(commandParts, commandParts[this.CommandKeyword.Name]);
                    if (cmd == null)
                    {
                        sbError.Append("Invalid command");
                    }
                }
            }
            else
            {
                sbError.AppendFormat("{0} was not specified", this.CommandKeyword.Name);
            }

            string errorString = sbError.ToString();
            if (cmd == null)
            {
                errorString = "Parameters were not specified";
            }
            return new CommandParseResult(cmd, errorString.Length>0 ? true: false, errorString);
        }

        public Command GetCommand(Dictionary<string, string> dicCommandParts, string commandName)
        {
            Command cmd = null;
            if (this.Commands.ContainsKey(commandName))
            {
                cmd = Activator.CreateInstance(this.Commands[commandName].CommandType) as Command;
                // Created the command object, now we need to pass the parameters
                // Lets go through the parameters and run the command
                PropertyInfo[] props = this.Commands[commandName].CommandType.GetProperties();

                foreach (PropertyInfo prop in props)
                {
                    object[] attrs = prop.GetCustomAttributes
                        (typeof(CommandParameterAttribute), true);
                    if (attrs.Length > 0)
                    {
                        CommandParameterAttribute attr = (CommandParameterAttribute)attrs[0];
                        string optionName = attr.CommandParameter;

                        if (dicCommandParts.ContainsKey(optionName))
                        {
                            CommandOption opt = this.Commands[commandName].Options[optionName];
                            if (opt.OptionType == CommandOption.CommandOptionType.ValueTypeBoolean)
                            {
                                prop.SetValue(cmd, opt.GetBoolValue(dicCommandParts[optionName]), (object[])null);
                            }
                            else if (opt.OptionType == CommandOption.CommandOptionType.ValueTypeInt)
                            {
                                prop.SetValue(cmd, opt.GetIntValue(dicCommandParts[optionName]), (object[])null);
                            }
                            else if (opt.OptionType == CommandOption.CommandOptionType.ValueTypeString ||
                                opt.OptionType == CommandOption.CommandOptionType.ValueTypeValidDirPath ||
                                opt.OptionType == CommandOption.CommandOptionType.ValueTypeValidFilePath||
                                opt.OptionType == CommandOption.CommandOptionType.ValueTypeStringSet)
                            {
                                prop.SetValue(cmd, dicCommandParts[optionName], (object[])null);
                            }
                        }
                        else
                        {
                        }
                    }
                }

            }

            return cmd;
        }

        private string ValidateCommandOptions(Dictionary<string, string> dicCommandParts, string commandName)
        {
            StringBuilder sbError = new StringBuilder();
            if (this.Commands.ContainsKey(commandName))
            {
                // Try to find the command definition.
                if (this.Commands.ContainsKey(commandName))
                {
                    CommandDefinition def = this.Commands[commandName];
                    bool commandIsValid = true;
                    foreach (CommandOption opt in def.Options.Values)
                    {
                        // Validate that all required parameters have been passed
                        if (dicCommandParts.ContainsKey(opt.Name))
                        {
                            if (!opt.IsValidValue(dicCommandParts[opt.Name]))
                            {
                                sbError.AppendFormat("Value for {0} was not invalid.", opt.Name);
                                return sbError.ToString();
                            }
                        }
                        else
                        {
                            if (!opt.IsOptional)
                            {
                                sbError.AppendFormat("Value for {0} is required.", opt.Name);
                                return sbError.ToString();
                            }
                        }
                    }
                    return sbError.ToString();
                }
            }
            return sbError.ToString();
        }
    }
}
